package helpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.mime.MimeTypeException;
import play.Logger;
import play.Play;
import play.api.libs.MimeTypes;
import service.GuiceInjectionPlugin;
import service.filestore.FileStore;
import service.filestore.roles.Admin;
import charts.builder.DataSource;
import charts.builder.spreadsheet.XlsDataSource;
import charts.builder.spreadsheet.XlsxDataSource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
public class FileStoreHelper {
public static final String XLSX_MIME_TYPE =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
public static final String XLS_MIME_TYPE =
"application/vnd.ms-excel";
public abstract static class FileOrFolderException extends Exception {
private static final long serialVersionUID = 1L;
public FileOrFolderException(String msg) { super(msg); }
}
public static class FileExistsException extends FileOrFolderException {
private static final long serialVersionUID = 1L;
public FileExistsException(FileStore.File f) {
super("File already exists: "+f.getPath());
}
}
public static class FolderExistsException extends FileOrFolderException {
private static final long serialVersionUID = 1L;
public FolderExistsException(FileStore.Folder f) {
super("Folder already exists: "+f.getPath());
}
}
public static class FolderNotFoundException extends FileOrFolderException {
private static final long serialVersionUID = 1L;
public FolderNotFoundException(final String name) {
super("Required folder does not exist: " + name);
}
}
protected final Session session;
protected final PrintWriter out;
public FileStoreHelper(final Session session) {
this(session, new PrintWriter(new OutputStreamWriter(System.out)));
}
public FileStoreHelper(final Session session, PrintWriter out) {
this.session = session;
this.out = out;
}
public FileStore.Folder mkdir(final String absPath, final boolean createParents)
throws FileExistsException, FolderExistsException,
FolderNotFoundException, RepositoryException {
final FileStore.Manager manager = fileStore().getManager(session);
FileStore.Folder folder = manager.getRoot();
final Iterator<String> iter = getPathParts(absPath).iterator();
String name;
while (iter.hasNext()) {
name = iter.next();
FileStore.FileOrFolder fof = folder.getFileOrFolder(name);
if (fof == null) {
// Only create if we can create parents or it's not a parent
if (createParents || !iter.hasNext()) {
out.println(String.format("Creating new folder in %s: %s",
folder.getPath(), name));
folder = folder.createFolder(name);
} else {
throw new FolderNotFoundException(name);
}
} else if (fof instanceof FileStore.Folder) {
folder = (FileStore.Folder) fof;
// Folder already exists
if (!iter.hasNext())
throw new FolderExistsException(folder);
} else {
throw new FileExistsException((FileStore.File) fof);
}
}
return folder;
}
public InputStream createZipFile(final FileStore.Folder folder)
throws IOException, RepositoryException {
final File tempFile = File.createTempFile("zipfile", "");
final ZipArchiveOutputStream zos =
ZipHelper.setupZipOutputStream(new FileOutputStream(tempFile));
addFolderToZip(zos, folder, folder);
zos.close();
return new FileInputStream(tempFile) {
@Override
public void close() throws IOException {
super.close();
tempFile.delete();
}
};
}
/**
* Get string to strip from the beginning of all zip paths, using the
* speficied base folder.
*
* The specified folder's name should be preserved in the zip file, except
* in the case of the root folder.
*
* @param folder Base folder to use
* @return Prefix to strip from filestore paths
*/
public String getZipPath(
final FileStore.FileOrFolder fof,
final FileStore.Folder baseFolder) {
if (baseFolder.getPath().equals("/")) {
// Paths must not have a leading slash
return fof.getPath().substring(1);
} else {
// To be user friendly, use single base directory
return baseFolder.getName() + fof.getPath().replaceFirst(
"^"+Pattern.quote(baseFolder.getPath()), "");
}
}
protected void addFolderToZip(final ZipArchiveOutputStream zos,
final FileStore.Folder folder,
final FileStore.Folder baseFolder)
throws IOException, RepositoryException {
for (final FileStore.Folder subFolder : folder.getFolders()) {
addFolderToZip(zos, subFolder, baseFolder);
}
for (final FileStore.File file : folder.getFiles()) {
addToZip(zos, getZipPath(file, baseFolder),
file.getMimeType(), file.getData());
}
}
protected void addToZip(final ZipArchiveOutputStream zos,
String filename, String mimeType,
InputStream data) throws IOException {
zos.putArchiveEntry(new ZipArchiveEntry(getNameWithExt(filename, mimeType)));
IOUtils.copy(data, zos);
zos.closeArchiveEntry();
data.close();
}
public static String getNameWithExt(String filename, String mimeType) {
// Check if the current extension is good enough
final scala.Option<String> expected = MimeTypes.forFileName(filename);
if (expected.isDefined() && expected.get().equals(mimeType)) {
return filename;
}
// We need to add on an extension that conveys the mime type.
try {
final org.apache.tika.mime.MimeTypes mimeTypes =
TikaConfig.getDefaultConfig().getMimeRepository();
final String ext = mimeTypes.forName(mimeType).getExtension();
if (!ext.isEmpty()) {
return filename+ext;
}
} catch (MimeTypeException e) {
// Fall through
}
// We gave it our best shot.
return filename;
}
protected Iterable<String> getPathParts(final String absPath) {
final Deque<String> q = new LinkedList<String>();
java.io.File f = new java.io.File(absPath);
do {
if (!f.getName().equals(""))
q.addFirst(f.getName());
f = f.getParentFile();
} while (f != null);
return q;
}
public void rm(final String path, final boolean recursive) throws RepositoryException {
if(path == null) {
return;
}
FileStore.FileOrFolder f = fileStore().getManager(session).getFileOrFolder(path);
if(f == null) {
out.println(String.format("no such file or directory '%s'", path));
} else if(f instanceof FileStore.Folder && !recursive) {
out.println(String.format(
"cannot remove '%s': Is a directory, try using --recursive", path));
} else {
f.delete();
}
}
public void mv(final String srcPath, final String destPath) throws RepositoryException {
FileStore.FileOrFolder src = fileStore().getManager(session).getFileOrFolder(srcPath);
if(src == null) {
out.println(String.format("file or folder %s not found", srcPath));
return;
}
FileStore.FileOrFolder dest = fileStore().getManager(session).getFileOrFolder(destPath);
if(dest == null) {
out.println(String.format("destination folder %s not found", destPath));
return;
}
if(dest instanceof FileStore.Folder) {
src.move((FileStore.Folder)dest);
} else {
out.println(String.format("destination %s not a folder", destPath));
}
}
private String format(FileStore.FileOrFolder f, boolean isFolder,
boolean showPerms, boolean ids) throws RepositoryException {
StringBuilder sb = new StringBuilder();
for(int i = 0;i<f.getDepth()-1;i++) {
sb.append("| ");
}
if(StringUtils.equals(f.getPath(), "/")) {
sb.append("/ (root)");
} else {
sb.append("|");
sb.append("-");
if(isFolder) {
sb.append("+");
} else {
sb.append("-");
}
sb.append(" ");
sb.append(f.getName());
if(showPerms && f instanceof FileStore.Folder) {
sb.append(" ");
sb.append(((FileStore.Folder)f).getGroupPermissions().toString());
}
}
if(ids) {
sb.append(" [");
sb.append(f.getIdentifier());
sb.append("]");
}
return sb.toString();
}
private void tree(final FileStore.Folder folder,
boolean showPerms, boolean ids) throws RepositoryException {
out.println(format(folder, true, showPerms, ids));
for(FileStore.Folder f: folder.getFolders()) {
tree(f, showPerms, ids);
}
for(FileStore.File f : folder.getFiles()) {
out.println(format(f, false, showPerms, ids));
}
}
public void tree(final String path, Boolean showPerms, Boolean showIds) throws RepositoryException {
boolean permissions = showPerms != null?showPerms.booleanValue():false;
boolean ids = showIds != null?showIds.booleanValue():false;
FileStore.Manager m = fileStore().getManager(session);
FileStore.FileOrFolder f = m.getFileOrFolder(StringUtils.isBlank(path) ? "/" : path);
if (f == null) {
out.println(String.format("no such folder %s", path));
} else if (f instanceof FileStore.File) {
out.println(String.format("%s is a file", path));
} else {
tree((FileStore.Folder) f, permissions, ids);
}
}
private void listAdminTree(Authorizable authorizable, int depth) throws RepositoryException {
boolean isGroup = authorizable instanceof Group;
String id = authorizable.getID();
String email = "";
try {
// Get user email if user
Node node = session.getNodeByIdentifier(id);
email = node.getProperty("email").getValue().getString();
} catch (Exception e) {}
StringBuilder sb = new StringBuilder();
for(int i = 0;i<depth-1;i++) {
sb.append("| ");
}
if(isGroup) {
sb.append("|-+ ");
sb.append(id);
} else {
sb.append("|-- ");
if(StringUtils.isNotBlank(email)) {
sb.append(email);
} else {
sb.append(id);
}
}
out.println(sb);
if(isGroup) {
Iterator<Authorizable> aIter = ((Group)authorizable).getDeclaredMembers();
while(aIter.hasNext()) {
Authorizable a = aIter.next();
listAdminTree(a, depth + 1);
}
}
}
public void listadmin(String name) throws RepositoryException {
Group root = Admin.getInstance(session).getGroup();
listAdminTree(root, 0);
}
private String makePath(String path1, String path2) {
if(StringUtils.endsWith(path1, "/")) {
return path1+path2;
} else {
return path1+"/"+path2;
}
}
public void importArchive(String archive, String path) {
if(StringUtils.isBlank(path)) {
path = "/";
}
final FileStore.Manager manager = fileStore().getManager(session);
File f = new File(archive);
try {
ZipInputStream zip = new ZipInputStream(new FileInputStream(f));
while(true) {
ZipEntry entry = zip.getNextEntry();
if(entry == null) {
break;
}
String entrypath = entry.getName();
String filename = FilenameUtils.getName(entrypath);
String parent = makePath(path, FilenameUtils.getFullPathNoEndSeparator(entrypath));
FileStore.Folder parentFolder = (service.filestore.FileStore.Folder)manager.getFileOrFolder(parent);
if(parentFolder == null) {
parentFolder = mkdir(parent, true);
}
if(!entry.isDirectory()) {
FileStore.File file = getFile(parentFolder, filename);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
IOUtils.copy(zip, bout);
byte[] buf = bout.toByteArray();
if(file == null) {
parentFolder.createFile(filename, getMimetype(filename), new ByteArrayInputStream(buf));
} else {
out.println(String.format("file %s already exists", filename));
}
}
}
zip.close();
} catch(Exception e) {
out.println(String.format("failed to import archive %s", archive));
e.printStackTrace(out);
}
}
public Map<FileStore.File, DataSource> getDatasources(
final Iterable<FileStore.File> files) throws Exception {
final ImmutableMap.Builder<FileStore.File,DataSource> b =
ImmutableMap.builder();
for (FileStore.File file : files) {
// Check this is a MS spreadsheet document (no chance otherwise)
if (file.getMimeType().equals(XLS_MIME_TYPE)) {
b.put(file, new XlsDataSource(file.getData()));
} else if (file.getMimeType().equals(XLSX_MIME_TYPE)) {
b.put(file, new XlsxDataSource(file.getData()));
}
}
return b.build();
}
public void printInfo(final String pathOrId) throws RepositoryException {
FileStore.FileOrFolder f = null;
try {
f = fileStore().getManager(session).getByIdentifier(pathOrId);
} catch(IllegalArgumentException e) {}
if(f == null) {
f = fileStore().getManager(session).getFileOrFolder(pathOrId);
}
if(f != null) {
out.println(f.getPath());
out.println("id " + f.getIdentifier());
if(f instanceof FileStore.File) {
FileStore.File file = (FileStore.File)f;
out.print("author ");
if(file.getAuthor() != null) {
out.println(file.getAuthor().getName()+" - "+file.getAuthor().getEmail());
} else {
out.println("unknown");
}
out.println("mimetype "+file.getMimeType());
out.print("modification time ");
if(file.getModificationTime()!= null) {
out.println(new SimpleDateFormat(
"EEE, d MMM yyyy HH:mm:ss zzzz").format(file.getModificationTime().getTime()));
} else {
out.println("unknown");
}
}
out.println("depth "+f.getDepth());
} else {
out.println(String.format("file or folder %s not found", pathOrId));
}
}
private FileStore.File getFile(FileStore.Folder folder, String name) throws RepositoryException {
for(FileStore.File file : folder.getFiles()) {
if(file.getName().equals(name)) {
return file;
}
}
return null;
}
private String getMimetype(String filename) {
final scala.Option<String> guessed =
MimeTypes.forFileName(filename);
if(guessed.nonEmpty()) {
return guessed.get();
} else {
Logger.warn(String.format("unable to determine mimetype for filename %s", filename));
return "application/octet-stream";
}
}
protected FileStore fileStore() {
return GuiceInjectionPlugin.getInjector(Play.application())
.getInstance(FileStore.class);
}
public List<FileStore.File> listFilesInFolder(FileStore.Folder folder)
throws RepositoryException {
final ImmutableList.Builder<FileStore.File> b = ImmutableList.builder();
b.addAll(sortFof(folder.getFiles()));
for (FileStore.Folder subfolder : sortFof(folder.getFolders())) {
b.addAll(sortFof(listFilesInFolder(subfolder)));
}
return b.build();
}
private <T extends FileStore.FileOrFolder> SortedSet<T> sortFof(
final Iterable<T> files) {
return ImmutableSortedSet.<T> orderedBy(new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
return o1.getName().compareTo(o2.getName());
}
}).addAll(files).build();
}
}